## 6.1. Файлы и каталоги.
6.1.1. Используя системный вызов stat, напишите программу, определяющую тип файла: обычный файл, каталог, устройство, FIFO-файл. Ответ:

    #include <sys/types.h>
    #include <sys/stat.h>

    typeOf( name ) char *name;
    {  int type; struct stat st;
       if( stat( name, &st ) < 0 ){
               printf( "%s не существует\n", name );
               return 0;
       }
       printf("Файл имеет %d имен\n", st.st_nlink);

       switch(type = (st.st_mode & S_IFMT)){
       case S_IFREG:
            printf( "Обычный файл размером %ld байт\n",
                       st.st_size ); break;
       case S_IFDIR:
               printf( "Каталог\n" );      break;
       case S_IFCHR:   /* байтоориентированное  */
       case S_IFBLK:   /* блочноориентированное */
               printf( "Устройство\n" );   break;
       case S_IFIFO:
               printf( "FIFO-файл\n" );    break;
       default:
               printf( "Другой тип\n" );   break;
       }       return type;
     }
6.1.2. Напишите программу, печатающую: свои аргументы, переменные окружения, информацию о всех открытых ею файлах и используемых трубах. Для этой цели используйте системный вызов

    struct stat st; int used, fd;
    for(fd=0; fd < NOFILE; fd++ ){
      used = fstat(fd, &st) < 0 ? 0 : 1;
      ...
    }
Программа может использовать дескрипторы файлов с номерами 0..NOFILE-1 (обычно 0..19). Если fstat для какого-то fd вернул код ошибки (<0), это означает, что данный дескриптор не связан с открытым файлом (т.е. не используется). NOFILE определено в include-файле <sys/param.h>, содержащем разнообразные параметры данной системы.

6.1.3. Напишите упрощенный аналог команды ls, распечатывающий содержимое текущего каталога (файла с именем ".") без сортировки имен по алфавиту. Предусмотрите чтение каталога, чье имя задается как аргумент программы. Имена "." и ".." не выдавать.

Формат каталога описан в header-файле <sys/dir.h> и в "канонической" версии выглядит так: каталог - это файл, состоящий из структур direct, каждая описывает одно имя файла, входящего в каталог:

    struct  direct {
       unsigned short d_ino;   /* 2 байта: номер I-узла */
       char    d_name[DIRSIZ]; /* имя файла             */
    };
В семействе BSD формат каталога несколько иной - там записи имеют разную длину, зависящую от длины имени файла, которое может иметь длину от 1 до 256 символов.

Имя файла может состоять из любых символов, кроме '\0', служащего признаком конца имени и '/', служащего разделителем. В имени допустимы пробелы, управляющие символы (но не рекомендуются!), любое число точек (в отличие от MS DOS, где допустима единственная точка, отделяющая собственно имя от суффикса (расширения)), разрешены даже непечатные (т.е. управляющие) символы! Если имя файла имеет длину 14 (DIRSIZ) символов, то оно не оканчивается байтом '\0'. В этом случае для печати имени файла возможны три подхода:

Выводить символы при помощи putchar()-а в цикле. Цикл прерывать по индексу равному DIRSIZ, либо по достижению байта '\0'.
Скопировать поле d_name в другое место:
         char buf[ DIRSIZ + 1 ];
         strncpy(buf, d.d_name, DIRSIZ);
         buf[ DIRSIZ ] = '\0';
Этот способ лучший, если имя файла надо не просто напечатать, но и запомнить на будущее, чтобы использовать в своей программе.

Использовать такую особенность функции printf():
         #include <sys/types.h>
         #include <sys/dir.h>

         struct direct d;
            ...
         printf( "%*.*s\n", DIRSIZ, DIRSIZ, d.d_name );
Если файл был стерт, то в поле d_ino записи каталога будет содержаться 0 (именно поэтому I-узлы нумеруются начиная с 1, а не с 0). При удалении файла содержимое его (блоки) уничтожается, I-узел освобождается, но имя в каталоге не затирается физически, а просто помечается как стертое: d_ino=0; Каталог при этом никак не уплотняется и не укорачивается! Поэтому имена с d_ino==0 выдавать не следует - это имена уже уничтоженных файлов.

При создании нового имени (creat, link, mknod) система просматривает каталог и переиспользует первый от начала свободный слот (ячейку каталога) где d_ino==0, записывая новое имя в него (только в этот момент старое имя-призрак окончательно исчезнет физически). Если пустых мест нет - каталог удлиняется.

Любой каталог всегда содержит два стандартных имени: "." - ссылка на этот же каталог (на его собственный I-node), ".." - на вышележащий каталог. У корневого каталога "/" оба этих имени ссылаются на него же самого (т.е. содержат d_ino==2).

Имя каталога не содержится в нем самом. Оно содержится в "родительском" каталоге ...

Каталог в UNIX - это обычный дисковый файл. Вы можете читать его из своих программ. Однако никто (включая суперпользователя*) не может записывать что-либо в каталог при помощи write. Изменения содержимого каталогов выполняет только ядро, отвечая на запросы в виде системных вызовов creat, unlink, link, mkdir, rmdir, rename, mknod. Коды доступа для каталога интерпретируются следующим образом:

w запись
S_IWRITE. Означает право создавать и уничтожать в каталоге имена файлов при помощи этих вызовов. То есть: право создавать, удалять и переименовывать файлы в каталоге. Отметим, что для переименования или удаления файла вам не требуется иметь доступ по записи к самому файлу - достаточно иметь доступ по записи к каталогу, содержащему его имя!
r чтение
S_IREAD. Право читать каталог как обычный файл (право выполнять opendir, см. ниже): благодаря этому мы можем получить список имен файлов, содержащихся в каталоге. Однако, если мы ЗАРАНЕЕ знаем имена файлов в каталоге, мы МОЖЕМ работать с ними - если имеем право доступа "выполнение" для этого каталога!
x выполнение
S_IEXEC. Разрешает поиск в каталоге. Для открытия файла, создания/удаления файла, перехода в другой каталог (chdir), система выполняет следующие действия (осуществляемые функцией namei() в ядре): чтение каталога и поиск в нем указанного имени файла или каталога; найденному имени соответствует номер I-узла d_ino; по номеру узла система считывает с диска сам I-узел нужного файла и по нему добирается до содержимого файла. Код "выполнение" - это как раз разрешение такого просмотра каталога системой. Если каталог имеет доступ на чтение - мы можем получить список файлов (т.е. применить команду ls); но если он при этом не имеет кода доступа "выполнение" - мы не сможем получить доступа ни к одному из файлов каталога (ни открыть, ни удалить, ни создать, ни сделать stat, ни chdir). Т.е. "чтение" разрешает применение вызова read, а "выполнение" - функции ядра namei. Фактически "выполнение" означает "доступ к файлам в данном каталоге"; еще более точно - к I-nodам файлов этого каталога.
t sticky bit
S_ISVTX - для каталога он означает, что удалить или переименовать некий файл в данном каталоге могут только: владелец каталога, владелец данного файла, суперпользователь. И никто другой. Это исключает удаление файлов чужими.
Совет: для каталога полезно иметь такие коды доступа:

    chmod o-w,+t каталог
В системах BSD используется, как уже было упомянуто, формат каталога с переменной длиной записей. Чтобы иметь удобный доступ к именам в каталоге, возникли специальные функции чтения каталога: opendir, closedir, readdir. Покажем, как простейшая команда ls реализуется через эти функции.

    #include <stdio.h>
    #include <sys/types.h>
    #include <dirent.h>

    int listdir(char *dirname){
        register struct dirent *dirbuf;
        DIR *fddir;
        ino_t dot_ino = 0, dotdot_ino = 0;

        if((fddir = opendir (dirname)) == NULL){
            fprintf(stderr, "Can't read %s\n", dirname);
            return 1;
        }
        /* Без сортировки по алфавиту */
        while ((dirbuf = readdir (fddir)) != NULL ) {
            if (dirbuf->d_ino == 0) continue;
            if (strcmp (dirbuf->d_name, "." ) == 0){
                    dot_ino = dirbuf->d_ino;
                    continue;
            } else if(strcmp (dirbuf->d_name, "..") == 0){
                    dotdot_ino = dirbuf->d_ino;
                    continue;
            } else printf("%s\n", dirbuf->d_name);
        }
        closedir (fddir);

        if(dot_ino    == 0) printf("Поврежденный каталог: нет имени \".\"\n");
        if(dotdot_ino == 0) printf("Поврежденный каталог: нет имени \"..\"\n");
        if(dot_ino && dot_ino == dotdot_ino)  printf("Это корневой каталог диска\n");

        return 0;
    }

    int main(int ac, char *av[]){
        int i;

        if(ac > 1) for(i=1; i < ac; i++) listdir(av[i]);
        else                             listdir(".");

        return 0;
    }
Обратите внимание, что тут не требуется добавление '\0' в конец поля d_name, поскольку его предоставляет нам сама функция readdir().

6.1.4. Напишите программу удаления файлов и каталогов, заданных в argv. Делайте stat, чтобы определить тип файла (файл/каталог). Программа должна отказываться удалять файлы устройств. Для удаления пустого каталога (не содержащего иных имен, кроме "." и "..") следует использовать сисвызов

    rmdir(имя_каталога);
(если каталог не пуст - errno получит значение EEXIST); а для удаления обычных файлов (не каталогов)

    unlink(имя_файла);
Программа должна запрашивать подтверждение на удаление каждого файла, выдавая его имя, тип, размер в килобайтах и вопрос "удалить ?".

* - Именно это имя показывает команда ps -ef

* - Собственно, операционная система характеризуется набором предоставляемых ею системных вызовов, поскольку все концепции, заложенные в системе, доступны нам только через них. Если мы имеем две реализации системы с разным внутренним устройством ядер, но предоставляющие одинаковый интерфейс системных вызовов (их набор, смысл и поведение), то это все-таки одна и та же система! Ядра могут не просто отличаться, но и быть построенными на совершенно различных принципах: так обстоит дело с UNIX-ами на однопроцессорных и многопроцессорных машинах. Но для нас ядро - это "черный ящик", полностью определяемый его поведением, т.е. своим интерфейсом с программами, но не внутренним устройством. Вторым параметром, характеризующим ОС, являются форматы данных, используемые системой: форматы данных для сисвызовов и формат информации в различных файлах, в том числе формат оформления выполняемых файлов (формат данных в физической памяти машины в этот список не входит - он зависим от реализации и от процессора). Как правило, программа пишется так, чтобы использовать соглашения, принятые в данной системе, для чего она просто включает ряд стандартных include-файлов с описанием этих форматов. Имена этих файлов также можно отнести к интерфейсу системы.

* - Таким как таблица процессов, таблица открытых файлов (всех вместе и для каждого процесса), и.т.п.

* - Суперпользователь (superuser) имеет uid==0. Это "привелегированный" пользователь, который имеет право делать ВСЕ. Ему доступны любые сисвызовы и файлы, несмотря на коды доступа и.т.п.